#!/usr/bin/env python


import os
import sys
import cmd
import socket
from optparse import OptionParser

from dexen.system.common import service
from dexen.system.common.meta import ScoreMeta
from dexen.system.common.states import JobState
from dexen.system.client.proxy import ServerProxy, JobProxy
from dexen.system.common.excepts import AllNodeBusyException

#===============================================================================
# Constants
#===============================================================================
MY_EXIT = -999
ARG_ERR_MSG = "Incorrect number of arguments"

#===============================================================================
# MyCmd
#===============================================================================
class MyCmd(cmd.Cmd):
    def __init__(self, server):
        cmd.Cmd.__init__(self)
        #self.server = ServerProxy()
        assert isinstance(server, ServerProxy)
        self.server = server
        self.prompt = "DEXEN>"
        self.jobs = {}
        
    def _get_job(self, job_id):
        if self.jobs.has_key(job_id):
            return self.jobs[job_id]
        job = self.server.get_job(job_id)
        assert job != None
        self.jobs[job_id] = job
        return job    
                    
    #Command: archive_server_addr / asa
    def do_asa(self, line):
        self.do_archive_server_addr(line)
        
    def do_archive_server_addr(self, line):
        parser = OptionParser()
        parser.prog = "archive_server_addr"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 0:
            parser.error(ARG_ERR_MSG)
        ip, port = self.server.get_address()
        msg = ""
        msg += "%s%s\n"%("archive server ip:".ljust(22),ip)
        msg += "%s%s\n"%("archive server port:".ljust(22), port)
        print msg
        print ""
        
    #Command: start_new_job / snj
    def do_snj(self, line):
        self.do_start_new_job(line)
        
    def do_start_new_job(self, line):
        self.server.check_connection()
        parser = OptionParser()
        parser.prog = "start_new_job"
        parser.set_usage("start_new_job JOB_NAME [-d JOB_DEF_DIR]")
        parser.add_option("-d", "--job-dir", dest="job_dir",
                          default=os.getenv("JOB_DEF_DIR"), metavar="JOB_DEF_DIR", 
                          help="root directory that contains all the source code of the job definition.")
        options, args = parser.parse_args(line.split())
        
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job_name = args[0]
        if not job_name.isalnum():
            parser.error("Job name should only contain alphanumeric characters.")
        if not options.job_dir:
            parser.error("The path of the JOB_DEF has to be provided.")
        if not os.path.exists(options.job_dir):
            parser.error("The specified path for the JOB_DEF does not exist.")
        self.server.start_job(job_name, options.job_dir)
        print "Job has been started."
        print ""
        
    #Command: stop_job / sj
    def do_sj(self, line):
        self.do_stop_job(line)
        
    def do_stop_job(self, line):
        self.server.check_connection()
        parser = OptionParser("stop_job JOB_ID")
        parser.prog = "stop_job"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        job.stop()
        #TODO: where is the print statement that prints "Job is already stopped."
        print "Job has been stopped."
        print ""
        
    #Command: del_job / dj
    def do_dj(self, line):
        self.do_del_job(line)
        
    def do_del_job(self, line):
        self.server.check_connection()
        parser = OptionParser("del_job JOB_ID")
        parser.prog = "del_job"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job_id = int(args[0])
        job = self._get_job(job_id)
        job.delete()
        del self.jobs[job_id]
        print "Job has been deleted."
        print ""
    
    #Command: list_jobs / lj
    def do_lj(self, line):
        self.do_list_jobs(line)
    
    def do_list_jobs(self, line):
        self.server.check_connection()
        parser = OptionParser()
        parser.prog = "list_jobs"
        jobs = self.server.get_job_states()
        if len(jobs) > 0:
            job_state = jobs[0]
            print job_state.get_header()
        else:
            print "No jobs exist."
        for job_state in jobs:
            assert isinstance(job_state, JobState)
            print job_state
        print ""
        print ""
        
    #Command: download_job_data / djd
    def do_djd(self, line):
        self.do_download_job_data(line)
        
    def do_download_job_data(self, line):
        self.server.check_connection()
        parser = OptionParser("download_job_data JOB_ID")
        parser.prog = "download_job_data"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        print "Downloading data from Server...."
        num_acts = job.update()
        if num_acts:
            print num_acts, "data items have been downloaded."
        else:
            print "No data items were downloaded."
        print ""
    
    #Command: get_job_log_file / gjlf
    def do_gjlf(self, line):
        self.do_get_job_log_file(line)
        
    def do_get_job_log_file(self, line):
        parser = OptionParser("get_job_log_file JOB_ID FILE_PATH")
        parser.prog = "get_job_log_file"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 2:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        f = open(args[1], "w")
        f.write(job.get_action_log())
        f.close()
        print "Log file has been created."
        print ""
        
    #Command: get_job_pareto_file / gjpf
    def do_gjpf(self, line):
        self.do_get_job_pareto_file(line)
    
    def do_get_job_pareto_file(self, line):
        parser = OptionParser("get_job_pareto_file JOB_ID FILE_PATH [-m MAX_LEVEL]")
        parser.prog = "get_job_pareto_file"
        parser.add_option("-m", "--max-level", metavar="max_level", dest="max_level",
                          default=1, type="int", 
                          help="rank until this level. Default max_level is 1.")
        (options, args) = parser.parse_args(line.split())
        if len(args) != 2:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        f = open(args[1], "w")
        f.write(job.get_pareto_log(options.max_level))
        f.close()
        print "Pareto file has been created."
        print ""
    
    #Command: get_job_step_file / gjsf
    def do_gjsf(self, line):
        self.do_get_job_step_file(line)
    
    def do_get_job_step_file(self, line):
        parser = OptionParser("get_job_step_file JOB_ID FILE_PATH STEP")
        parser.prog = "get_job_step_file"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 3:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        assert isinstance(job, JobProxy)
        f = open(args[1], "w")
        f.write(job.get_progress_log(int(args[2])))
        f.close()
        print "Progress file has been created.\n"
    
    #Command: add_job_score / ajs
    def do_ajs(self, line):
        self.do_add_job_score(line)
        
    def do_add_job_score(self, line):
        parser = OptionParser("add_job_score JOB_ID NAME [MIN|MAX]")
        parser.prog = "add_job_score"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 3:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        job.add_score(*args[1:])
        print "Score has been added."
        print ""
    
    #Command: del_job_score / djs
    def do_djs(self, line):
        self.do_del_job_score(line)
        
    def do_del_job_score(self, line):
        parser = OptionParser("del_job_score JOB_ID NAME")
        parser.prog = "del_job_score"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 2:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))     
        job.del_score(args[1])
        print "Score has been deleted."
        print ""
    
    #Command: list_job_scores / ljs
    def do_ljs(self, line):
        self.do_list_job_scores(line)
        
    def do_list_job_scores(self, line):
        parser = OptionParser("list_job_scores JOB_ID")
        parser.prog = "list_job_scores"
        (options, args) = parser.parse_args(line.split())
        if len(args) != 1:
            parser.error(ARG_ERR_MSG)
        job = self._get_job(int(args[0]))
        scores = job.get_score_metas()
        if len(scores) > 0:
            score_meta = scores[0]
            print score_meta.get_header()
        else:
            print "No job scores exist."
        for score_meta in scores:
            assert isinstance(score_meta, ScoreMeta)
            print score_meta
        print ""
       
    #Command: exit / e
    def do_e(self, line):
        self.do_exit(line)
        
    def do_exit(self, line):
        sys.exit(MY_EXIT)
    
    #Command: help / h
    def do_h(self, line):
        self.do_help(line)
        
    def do_help(self, line):
        msg = "%s\n"%(" DEXEN CLI COMMAND LIST ".center(80,'-'))
        msg += "%sGet the archive IP and port number.\n\n"%("archive_server_addr".ljust(40))
        msg += "%sStart a new job on the server.\n\n"%("start_new_job JOB_NAME SOURCE_PATH".ljust(40))
        msg += "%sStop a job running on the server.\n\n"%("stop_job JOB_ID".ljust(40))
        msg += "%sDelete a job on the server.\n\n"%("del_job JOB_ID".ljust(40))
        msg += "%sList all the jobs on the server.\n\n"%("list_jobs".ljust(40))
        msg += "%sDownload all data for a job.\n\n"%("download_job_data JOB_ID".ljust(40))
        msg += "%sCreate a log file for a job.\n\n"%("get_job_log_file JOB_ID FILE_PATH".ljust(40)) 
        msg += "%sCreate a pareto file for a job.\n\n"%("get_job_pareto_file JOB_ID FILE_PATH".ljust(40))
        msg += "%sCreate a progress file for a job.\n\n"%("get_job_step_file JOB_ID FILE_PATH STEP".ljust(40))
        msg += "%sAdd the name of a score.\n\n"%("add_job_score JOB_ID NAME [MIN|MAX]".ljust(40))
        msg += "%sDelete the name of a score.\n\n"%("del_job_score JOB_ID NAME".ljust(40))
        msg += "%sList all the added scores.\n\n"%("list_job_scores JOB_ID".ljust(40))
        msg += "%sExit the CLI.\n\n"%("exit".ljust(40))
        msg += "Commands can also be specified using a short form, "
        msg += "where only the first letter of each word is used. "
        msg += "For example, the short form of 'start_new_job' is 'snj'.\n\n"
        msg += "%s"%("...".center(80,'-'))
        print msg
    
#===============================================================================
# Main
#===============================================================================
def start():
    try:
        c.cmdloop("")
    except SystemExit, e:
        if e.code == MY_EXIT:
            print "Exiting.\n"
        else:
            start()
    except KeyboardInterrupt:
        print "Keyboard interrupted.\n"
        print ""
    except socket.error:
        print "Server is not reachable.\n"
        print ""
        start()        
    except EOFError:
        print "EOFError: ServerDisconnected.\n"
        print ""
        start()
    except Exception, e:
        print e
        if isinstance(e, AllNodeBusyException):
            print "Please start a new node or try again later.\n"
        print ""
        start()
    except:
        print "Unknown Exception.\n"
        print ""
        start()

def main():
    global c
    parser = OptionParser()
    parser.prog = "cli.py"
    parser.add_option("-i", "--server-ip", dest="reg_ip",
                      default=service.get_my_ip(), metavar="SERVER_IP",
                      help="connect to a server by IP address default to localhost's IP.")
    parser.add_option("-n", "--server-name", dest="reg_name", metavar="SERVER_NAME",
                  default="dexen_server", help="Connect to a server by name.")        
    (options, args) = parser.parse_args()
    server = ServerProxy()
    try:
        print "Trying to connect to a server..."
        server.connect(options.reg_ip, options.reg_name, options.reg_name == "local")
    except socket.error:
        print "Server is not reachable.\n"
        sys.exit()
    print "Connected."
    print ""    
    msg = "%s"%(" Welcome to DEXEN CLI ".center(60, '*'))
    print msg 
    c = MyCmd(server)
    start()  

if __name__ == "__main__":
    main()

